#include "zmodem.h"

static uint16_t crctab[256] = {
	0x0000,  0x1021,  0x2042,  0x3063,  0x4084,  0x50a5,  0x60c6,  0x70e7,
	0x8108,  0x9129,  0xa14a,  0xb16b,  0xc18c,  0xd1ad,  0xe1ce,  0xf1ef,
	0x1231,  0x0210,  0x3273,  0x2252,  0x52b5,  0x4294,  0x72f7,  0x62d6,
	0x9339,  0x8318,  0xb37b,  0xa35a,  0xd3bd,  0xc39c,  0xf3ff,  0xe3de,
	0x2462,  0x3443,  0x0420,  0x1401,  0x64e6,  0x74c7,  0x44a4,  0x5485,
	0xa56a,  0xb54b,  0x8528,  0x9509,  0xe5ee,  0xf5cf,  0xc5ac,  0xd58d,
	0x3653,  0x2672,  0x1611,  0x0630,  0x76d7,  0x66f6,  0x5695,  0x46b4,
	0xb75b,  0xa77a,  0x9719,  0x8738,  0xf7df,  0xe7fe,  0xd79d,  0xc7bc,
	0x48c4,  0x58e5,  0x6886,  0x78a7,  0x0840,  0x1861,  0x2802,  0x3823,
	0xc9cc,  0xd9ed,  0xe98e,  0xf9af,  0x8948,  0x9969,  0xa90a,  0xb92b,
	0x5af5,  0x4ad4,  0x7ab7,  0x6a96,  0x1a71,  0x0a50,  0x3a33,  0x2a12,
	0xdbfd,  0xcbdc,  0xfbbf,  0xeb9e,  0x9b79,  0x8b58,  0xbb3b,  0xab1a,
	0x6ca6,  0x7c87,  0x4ce4,  0x5cc5,  0x2c22,  0x3c03,  0x0c60,  0x1c41,
	0xedae,  0xfd8f,  0xcdec,  0xddcd,  0xad2a,  0xbd0b,  0x8d68,  0x9d49,
	0x7e97,  0x6eb6,  0x5ed5,  0x4ef4,  0x3e13,  0x2e32,  0x1e51,  0x0e70,
	0xff9f,  0xefbe,  0xdfdd,  0xcffc,  0xbf1b,  0xaf3a,  0x9f59,  0x8f78,
	0x9188,  0x81a9,  0xb1ca,  0xa1eb,  0xd10c,  0xc12d,  0xf14e,  0xe16f,
	0x1080,  0x00a1,  0x30c2,  0x20e3,  0x5004,  0x4025,  0x7046,  0x6067,
	0x83b9,  0x9398,  0xa3fb,  0xb3da,  0xc33d,  0xd31c,  0xe37f,  0xf35e,
	0x02b1,  0x1290,  0x22f3,  0x32d2,  0x4235,  0x5214,  0x6277,  0x7256,
	0xb5ea,  0xa5cb,  0x95a8,  0x8589,  0xf56e,  0xe54f,  0xd52c,  0xc50d,
	0x34e2,  0x24c3,  0x14a0,  0x0481,  0x7466,  0x6447,  0x5424,  0x4405,
	0xa7db,  0xb7fa,  0x8799,  0x97b8,  0xe75f,  0xf77e,  0xc71d,  0xd73c,
	0x26d3,  0x36f2,  0x0691,  0x16b0,  0x6657,  0x7676,  0x4615,  0x5634,
	0xd94c,  0xc96d,  0xf90e,  0xe92f,  0x99c8,  0x89e9,  0xb98a,  0xa9ab,
	0x5844,  0x4865,  0x7806,  0x6827,  0x18c0,  0x08e1,  0x3882,  0x28a3,
	0xcb7d,  0xdb5c,  0xeb3f,  0xfb1e,  0x8bf9,  0x9bd8,  0xabbb,  0xbb9a,
	0x4a75,  0x5a54,  0x6a37,  0x7a16,  0x0af1,  0x1ad0,  0x2ab3,  0x3a92,
	0xfd2e,  0xed0f,  0xdd6c,  0xcd4d,  0xbdaa,  0xad8b,  0x9de8,  0x8dc9,
	0x7c26,  0x6c07,  0x5c64,  0x4c45,  0x3ca2,  0x2c83,  0x1ce0,  0x0cc1,
	0xef1f,  0xff3e,  0xcf5d,  0xdf7c,  0xaf9b,  0xbfba,  0x8fd9,  0x9ff8,
	0x6e17,  0x7e36,  0x4e55,  0x5e74,  0x2e93,  0x3eb2,  0x0ed1,  0x1ef0
};

inline uint16_t updcrc(uint8_t cp, uint16_t crc) {
	return crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp;
}

inline uint16_t updcrc(const void *cp, size_t len, uint16_t crc) {
	auto data =(const uint8_t *)cp;
	for (size_t idx=0; idx<len;++idx) {
		crc = updcrc(data[idx], crc);
	}
	return crc;
}

class szrz_handler {
	typedef szrz_handler _this_t;
	bool _receive_data{false};	// begin to receive file
	bool _sending_data{false};	// begin to sending file
	enum : uint32_t {
		MODE_NONE = 0,
		MODE_RZ = 1,	// work as rz
		MODE_SZ = 2,	// work as sz
		MODE_SZ_ZFILE = 3,	// work as sz, ZFILE sent
		MODE_FIN = UINT32_MAX,
	} _working_mode{MODE_NONE}; // in receiving mode
	uint64_t _file_bytes{0};
	uint64_t _file_time{0};
	uint64_t _trans_bytes{0};
	uint64_t _trans_begin{0};
	uint64_t _trans_begin_bytes{0};
	file::file_t _fp{nullptr};
	std::string _current_file_name;
	std::string _current_file_path;
	std::string _output_data;
	uint32_t _resume_point{0};
	std::list<std::wstring> _sending_files;
	typedef struct {
#pragma pack(1)
		zmodem::frame_type type;
		union {
			uint32_t flags;
			uint8_t data[4];
		};
		union {
			uint16_t crc16;
			uint32_t crc32;
		};
		enum : uint8_t {
			ZHEX,
			ZBIN16,
			ZBIN32,
		} mode;
#pragma pack()
	} zmodem_frame_t;
	struct : public zmodem_frame_t {
		uint16_t read_bytes;
		std::string ext_data;
		uint32_t ext_data_crc;
		void reset() {
			type = zmodem::ZRQINIT;
			flags = 0;
			crc32 = 0;
			mode = zmodem_frame_t::ZHEX;
			read_bytes = 0;
			ext_data.clear();
			ext_data_crc = 0;
		}
		uint8_t &operator[](size_t offset) {
			assert(offset < 9);
			return ((uint8_t*)(&type))[offset];
		}
	} _zframe;
	static uint8_t _hex2u8(char ch) {
		if (ch >= '0' && ch <= '9') {
			return ch-'0';
		}
		if (ch >= 'A' && ch <= 'Z') {
			return ch-'A' + 10;
		}
		if (ch >= 'a' && ch <= 'z') {
			return ch-'a' + 10;
		}
		return 0xFF;
	}

	static auto to_sys(auto &&t) {
		using namespace std::chrono;
#if IS_GCC
		return file_clock::to_sys(t);
#elif (IS_CLANG && !IS_CLANG_MSC)
		return system_clock::time_point(duration_cast<system_clock::duration>(t.time_since_epoch()));
#else
		return clock_cast<system_clock>(t);
#endif
	}

	static auto from_sys(auto &&t) {
		using namespace std::chrono;
#if IS_GCC || (IS_CLANG && !IS_CLANG_MSC)
		return file_clock::from_sys(t);
#else
		return clock_cast<file_clock>(t);
#endif
	}

	void _update_progress() {
		auto cur = GetTickCount64();
		auto delta_time = cur - _trans_begin;
		if (delta_time > 500) {
			auto delta_bytes = _trans_bytes - _trans_begin_bytes;
			_trans_begin_bytes = _trans_bytes;
			_trans_begin = cur;
			double byte_per_sec = delta_bytes*1000.0/delta_time;

			if (_on_progress) {
				_on_progress(_working_mode == MODE_RZ, _current_file_name, _current_file_path, _file_bytes, _trans_bytes, byte_per_sec);
			}
		}
	}

	void _reset() {
		_working_mode = MODE_NONE;
		_receive_data = false;
		_sending_data = false;
		_current_file_name.clear();
		_current_file_path.clear();
		_file_bytes = 0;
		_trans_bytes = 0;
		_resume_point = 0;
		_trans_begin = 0;
		_trans_begin_bytes = 0;
		_bin_crc = 0;
		_sending_files.clear();
		if (_fp) {
			file::close(_fp);
			_fp = nullptr;
		}
	}

	static const char *_zframe_name(zmodem::frame_type type) {
		static const char *NAMES[] = {
			"ZRQINIT",
			"ZRINIT",
			"ZSINIT",
			"ZACK",
			"ZFILE",
			"ZSKIP",
			"ZNAK",
			"ZABORT",
			"ZFIN",
			"ZRPOS",
			"ZDATA",
			"ZEOF",
			"ZFERR",
			"ZCRC",
			"ZCHALLENGE",
			"ZCOMPL",
			"ZCAN",
			"ZFREECNT",
			"ZCOMMAND",
			"ZSTDERR",
		};
		return NAMES[type];
	}

	uint32_t _bin_crc = 0;

	void _write_hex_frame(zmodem::frame_type type, uint32_t flag = 0) {
#if DEBUG
		printf("<==ZFrame:%s(%d) MODE:HEX %u\n", _zframe_name(type), type, flag);
#endif
		_write_hex_frame_impl(type, (flag>>0)&0xFF, (flag>>8)&0xFF, (flag>>16)&0xFF, (flag>>24)&0xFF);
	}

	void _write_hex_frame(zmodem::frame_type type, uint8_t zf0, uint8_t zf1, uint8_t zf2, uint8_t zf3) {
#if DEBUG
		printf("<==ZFrame:%s(%d) MODE:HEX\n", _zframe_name(type), type);
#endif
		_write_hex_frame_impl(type, zf0, zf1, zf2, zf3);
	}

	void _write_hex_frame_impl(zmodem::frame_type type, uint8_t zf0, uint8_t zf1, uint8_t zf2, uint8_t zf3) {
		uint16_t crc = updcrc(type, 0);
		crc = updcrc(zf0, crc);
		crc = updcrc(zf1, crc);
		crc = updcrc(zf2, crc);
		crc = updcrc(zf3, crc);
		crc = updcrc(0, crc);
		crc = updcrc(0, crc);
		char buffer[64];
		auto len = cpp::base::io::sprintf(buffer, "**%cB%02x%02x%02x%02x%02x%02x%02x\r\x8a\x11"
			, zmodem::ZDLE
			, type
			, zf0, zf1, zf2, zf3
			, (crc>>8)&0xFF, crc&0xFF
		);
		_output_data.append(buffer, len);
	}

	void _write_bin16_frame(zmodem::frame_type type, uint32_t flag = 0) {
#if DEBUG
		printf("<==ZFrame:%s(%d) MODE:BIN16 %u\n", _zframe_name(type), type, flag);
#endif
		_write_bin16_frame_impl(type, (flag>>0)&0xFF, (flag>>8)&0xFF, (flag>>16)&0xFF, (flag>>24)&0xFF);
	}

	void _write_bin16_frame(zmodem::frame_type type, uint8_t zf0, uint8_t zf1, uint8_t zf2, uint8_t zf3) {
#if DEBUG
		printf("<==ZFrame:%s(%d) MODE:BIN16\n", _zframe_name(type), type);
#endif
		_write_bin16_frame_impl(type, zf0, zf1, zf2, zf3);
	}
	void _write_bin16_frame_impl(zmodem::frame_type type, uint8_t zf0, uint8_t zf1, uint8_t zf2, uint8_t zf3) {
		uint16_t crc = updcrc(type, 0);
		crc = updcrc(zf0, crc);
		crc = updcrc(zf1, crc);
		crc = updcrc(zf2, crc);
		crc = updcrc(zf3, crc);
		crc = updcrc(0, crc);
		crc = updcrc(0, crc);
		_output_data.push_back('*');
		_output_data.push_back(zmodem::ZDLE);
		_output_data.push_back('A');
		_output_data.push_back(uint8_t(type));
		_zenc_write(zf0);
		_zenc_write(zf1);
		_zenc_write(zf2);
		_zenc_write(zf3);
		_zenc_write(crc>>8);
		_zenc_write(crc&0xFF);
	}

	void _zenc_write(uint8_t ch) {
		if (ch&0x60) {
			_output_data.push_back(ch);
			return;
		}
		static const bool ctrl_escape = true;
		static const bool turbo_escape = true;
		switch(ch) {
		case zmodem::ZDLE:
			_output_data.push_back(zmodem::ZDLE);
			_output_data.push_back(zmodem::ZDLEE);
			break;
		case zmodem::XOFF:
		case zmodem::XON:
		case zmodem::XOFF|0x40:
		case zmodem::XON|0x40:
			_output_data.push_back(zmodem::ZDLE);
			_output_data.push_back(ch^0x40);
			break;
		case 0x10:
		case 0x90:
			if (turbo_escape) {
				_output_data.push_back(ch);
			} else {
				_output_data.push_back(zmodem::ZDLE);
				_output_data.push_back(ch^0x40);
			}
			break;
		case 0x0D:
		case 0x8D:
			if (ctrl_escape) {
				_output_data.push_back(zmodem::ZDLE);
				_output_data.push_back(ch^0x40);
			} else if (turbo_escape) {
				_output_data.push_back(ch);
			} else {
				// mode 2
				if (!_output_data.empty() && (_output_data.back()&0x7F) == '@') {
					_output_data.push_back(zmodem::ZDLE);
					_output_data.push_back(ch^0x40);
				} else {
					_output_data.push_back(ch);
				}
			}
			break;
		default:
			if (ctrl_escape) {
				_output_data.push_back(zmodem::ZDLE);
				_output_data.push_back(ch^0x40);
			} else {
				_output_data.push_back(ch);
			}
		}
	}

	void _write_bin16_data(const char *data, size_t len) {
		_bin_crc = updcrc(data, (uint32_t)len, _bin_crc);
		for (size_t idx=0; idx<len;++idx) {
			_zenc_write(((uint8_t*)data)[idx]);
		}
	}

	void _write_bin16_data_end(uint8_t type) {
		_output_data.push_back(zmodem::XON);
		_output_data.push_back(zmodem::ZDLE);
		_output_data.push_back(type);
		_bin_crc = updcrc(type, _bin_crc);
		_bin_crc = updcrc(0, updcrc(0, _bin_crc));
		_zenc_write((_bin_crc>>8)&0xFF);
		_zenc_write(_bin_crc&0xFF);
		_bin_crc = 0;
	}

	void _do_send_file() {
		if (!_sending_data || _fp == nullptr) {
			return;
		}
		uint8_t buf[8192];
		auto len = file::read(buf, 1, sizeof(buf), _fp);
		_write_bin16_data((const char *)buf, len);
		_trans_bytes += len;
		if (file::eof(_fp)) {
			_write_bin16_data_end(zmodem::ZCRCE);
			_write_hex_frame(zmodem::ZEOF, uint32_t(_file_bytes));
			_working_mode = MODE_SZ;
			_sending_data = false;
#if _DEBUG
			_log_printf("Send Finished: %llu/%llu, last_pkt:%lld\n",  _trans_bytes, _file_bytes, int64_t(len));
#endif
		} else {
			_write_bin16_data_end(zmodem::ZCRCG);
		}
		_update_progress();
	}

	bool _begin_send_file() {
		std::wstring file_path;
__begin_send_file:
		if (!_sending_files.empty()) {
			while(!_sending_files.empty()) {
				file_path = std::move(_sending_files.front());
				_sending_files.pop_front();
				_fp = file::open(file_path.c_str(), L"rb");
				if (!_fp && !_sending_files.empty()) {
					_log_printf(L"\x1b[41;37mCan NOT open file: %ls\x1b[0m\n", file_path.c_str());
					continue;
				}
				break;
			}
		} else {
			if (!_on_send_file || !_on_send_file(file_path)) {
				return false;
			}
			if (file_path.empty()) {
				if (_sending_files.empty()) {
					return false;
				}
				goto __begin_send_file;
			}
			_fp = file::open(file_path.c_str(), L"rb");
		}
		if (!_fp) {
			_log_printf(L"\x1b[41;37mCan NOT open file: %ls\x1b[0m\n", file_path.c_str());
			return false;
		}

		namespace fs = std::filesystem;
		_current_file_name = (const char*)fs::path(file_path).filename().generic_u8string().c_str();
		_current_file_path = (const char*)fs::path(file_path).generic_u8string().c_str();
		_file_bytes = fs::file_size(file_path);
		_file_time = std::chrono::system_clock::to_time_t(to_sys(fs::last_write_time(file_path)));

		_write_bin16_frame(zmodem::ZFILE);
		_write_bin16_data(_current_file_name.data(), _current_file_name.size()+1);
		char ext_info[256] = {};
		char file_time[64] = {};
		cpp::base::str::convert(_file_time, file_time, 8);
		_write_bin16_data(ext_info, sprintf(ext_info, "%llu %s 500", _file_bytes, file_time)+1);
#if _DEBUG
		_log_printf(L"ZFile: %ls, ", file_path.c_str());
		_log_printf("%s\n", ext_info);
#endif
		_write_bin16_data_end(zmodem::ZCRCW);
		_output_data.push_back(zmodem::XON);
		_working_mode = MODE_SZ_ZFILE;
		return true;
	}

	size_t _handle_packet(uint8_t *buf, size_t len) {
		size_t consumed_bytes = 0;
		while(len) {
			if (_receive_data) {
				// scan for \x18
				size_t idx = 0;
				std::string write_cache;
				write_cache.reserve(68*1024);
				auto resume = _resume_point;
				_resume_point = 0;
				if (resume>0) {
					switch(resume) {
					case 1:
						goto __resume_point_1;
					case 2:
						goto __resume_point_2;
					case 3:
						goto __resume_point_3;
					case 4:
						goto __resume_point_4;
					case 5:
						goto __resume_point_5;
					case 6:
						goto __resume_point_6;
					case 7:
						goto __resume_point_7;
					case 8:
						goto __resume_point_8;
					case 9:
						goto __resume_point_9;
					}
				}

				for (;idx<len && _receive_data;++idx) {
					if (buf[idx] != zmodem::ZDLE) {
						write_cache.push_back(buf[idx]);
						++_trans_bytes;
						if (write_cache.size() > 64*1024) {
							file::write(write_cache.data(), 1, write_cache.size(), _fp);
							write_cache.resize(0);
						}
						continue;
					}
					++idx;
					if (idx >= len) {
						_resume_point = 1;
						goto __end_for;
					}
__resume_point_1:
					assert(idx < len);
					switch(buf[idx]) {
					case zmodem::ZCRCG:
						{
							++idx;
__resume_point_2:
							if (idx>=len) {
								_resume_point = 2;
								goto __end_for;
							}

							if (buf[idx] == zmodem::ZDLE) {
								// escaped crc
								++idx;
							}
__resume_point_8:
							if (idx>=len) {
								_resume_point = 8;
								goto __end_for;
							}
							++idx;
__resume_point_3:
							if (idx >= len) {
								_resume_point = 3;
								goto __end_for;
							}
							if (buf[idx] == zmodem::ZDLE) {
								// escaped crc
								++idx;
							}
__resume_point_4:
							if (idx >= len) {
								_resume_point = 4;
								goto __end_for;
							}
						}
						break;
					case zmodem::ZCRCE:
						{
							++idx;
__resume_point_5:
							if (idx>=len) {
								_resume_point = 5;
								goto __end_for;
							}
							if (buf[idx] == zmodem::ZDLE) {
								// escaped crc
								++idx;
							}
__resume_point_9:
							if (idx>=len) {
								_resume_point = 9;
								goto __end_for;
							}
							++idx;
__resume_point_6:
							if (idx >= len) {
								_resume_point = 6;
								goto __end_for;
							}
							if (buf[idx] == zmodem::ZDLE) {
								// escaped crc
								++idx;
							}
__resume_point_7:
							if (idx >= len) {
								_resume_point = 7;
								goto __end_for;
							}
							// finished, wait next header
							_receive_data = false;
						}
						break;
					case 0x6c:// 0x7f
						++_trans_bytes;
						write_cache.push_back(0x7f);
						break;
					case 0x6d: // 0xff;
						++_trans_bytes;
						write_cache.push_back(char(0xff));
						break;
					default:
						if ((buf[idx]&0x60) == 0x40) {
							write_cache.push_back(buf[idx]^0x40);
						} else {
#if _DEBUG
							_log_printf("unknown escape! %02X\n", buf[idx]);
#endif
						}
						++_trans_bytes;
						break;
					}
					assert(idx < len);
				}
__end_for:
				if (!write_cache.empty()) {
					file::write(write_cache.data(), 1, write_cache.size(), _fp);
				}

				consumed_bytes += idx;
				assert(idx <= len);
				len -= idx;
				buf += idx;

				_update_progress();
				continue;
			}
			{
				size_t idx = 0;
				if (_resume_point) {
					auto resume = _resume_point;
					_resume_point = 0;
					switch(resume) {
					case 1:
						goto __zframe_resume_point_1;
					case 2:
						goto __zframe_resume_point_2;
					case 3:
						goto __zframe_resume_point_3;
					case 4:
						goto __zframe_resume_point_4;
					case 5:
						goto __zframe_resume_point_5;
					case 6:
						goto __zframe_resume_point_6;
					case 7:
						goto __zframe_resume_point_7;
					case 8:
						goto __zframe_resume_point_8;
					case 9:
						goto __zframe_resume_point_9;
					case 10:
						goto __zframe_resume_point_10;
					case 11:
						goto __zframe_resume_point_11;
					case 12:
						goto __zframe_resume_point_12;
					case 13:
						goto __zframe_resume_point_13;
					case 14:
						goto __zframe_resume_point_14;
					}
				}
				if (buf[idx] == '*') {
					++idx;
					_zframe.reset();
__zframe_resume_point_1:
					if (idx >= len) {
						_resume_point = 1;
						goto __zframe_end;
					}
					if (buf[idx] == '*') {
						// hex frame?
						++idx;
__zframe_resume_point_2:
						if (idx >= len) {
							_resume_point = 2;
							goto __zframe_end;
						}

						if (buf[idx] != zmodem::ZDLE) {
							// invalid hex frame
							goto __zframe_error;
						}

						++idx;
__zframe_resume_point_10:
						if (idx >= len) {
							_resume_point = 10;
							goto __zframe_end;
						}
						if (buf[idx] != 'B') {
							// invalid hex frame
							goto __zframe_error;
						}
						_zframe.mode = zmodem_frame_t::ZHEX;
						++idx;
						while(_zframe.read_bytes < 14) {
__zframe_resume_point_3:
							if (idx >= len) {
								_resume_point = 3;
								goto __zframe_end;
							}
							if (_zframe.read_bytes%2) {
								_zframe[_zframe.read_bytes/2] |= (_hex2u8(buf[idx]))&0x0F;
							} else {
								_zframe[_zframe.read_bytes/2] |= (_hex2u8(buf[idx])*16)&0xF0;
							}
							++_zframe.read_bytes;
							++idx;
						}
__zframe_resume_point_4:
						if (idx >= len) {
							_resume_point = 4;
							goto __zframe_end;
						}
						if (buf[idx] != '\r') {
							goto __zframe_error;
						}
						++idx;
__zframe_resume_point_5:
						if (idx >= len) {
							_resume_point = 5;
							goto __zframe_end;
						}
						if (buf[idx] != 0x8a) {
							goto __zframe_error;
						}
						++idx;
__zframe_resume_point_6:
						if (idx >= len) {
							_resume_point = 6;
							goto __zframe_end;
						}
						if (buf[idx] == 0x11) {
							// consume the optional 0x11
							++idx;
						}
						// hex frame finished
						goto __zframe_end;
					} else {
						if (buf[idx] != zmodem::ZDLE) {
							// invalid hex frame
							goto __zframe_error;
						}
						++idx;
__zframe_resume_point_11:
						if (idx >= len) {
							_resume_point = 11;
							goto __zframe_end;
						}
						if (buf[idx] == 'A') {
							// BIN16 frame
							++idx;
							_zframe.mode = zmodem_frame_t::ZBIN16;
							while(_zframe.read_bytes < 7) {
__zframe_resume_point_7:
								if (idx >= len) {
									_resume_point = 7;
									goto __zframe_end;
								}
								if (buf[idx] == zmodem::ZDLE) {
									++idx;
__zframe_resume_point_12:
									if (idx >= len) {
										_resume_point = 12;
										goto __zframe_end;
									}
									_zframe[_zframe.read_bytes] = buf[idx]^0x40;
								} else {
									_zframe[_zframe.read_bytes] = buf[idx];
								}
								++_zframe.read_bytes;
								++idx;
							}
							// bin16 frame header end
							if (_zframe.type == zmodem::ZFILE) {
								// read extend data

								while(true) {
__zframe_resume_point_8:
									if (idx >= len) {
										_resume_point = 8;
										goto __zframe_end;
									}

									if (buf[idx] != zmodem::ZDLE) {
										_zframe.ext_data.push_back(buf[idx]);
										++idx;
										continue;
									}
									++idx;
__zframe_resume_point_9:
									if (idx >= len) {
										_resume_point = 9;
										goto __zframe_end;
									}
									if (buf[idx] != zmodem::ZCRCW) {
										_zframe.ext_data.push_back(buf[idx]^0x40);
										++idx;
										continue;
									}
									// end of extend data
									_zframe.read_bytes = 0;
									while(_zframe.read_bytes < 2) {
										++idx;
__zframe_resume_point_13:
										if (idx >= len) {
											_resume_point = 13;
											goto __zframe_end;
										}
										if (buf[idx] != zmodem::ZDLE) {
											((uint8_t*)(&_zframe.ext_data_crc))[_zframe.read_bytes] = buf[idx];
											++_zframe.read_bytes;
											continue;
										}
										++idx;
__zframe_resume_point_14:
										if (idx >= len) {
											_resume_point = 14;
											goto __zframe_end;
										}
										((uint8_t*)(&_zframe.ext_data_crc))[_zframe.read_bytes] = buf[idx]^0x40;
										++_zframe.read_bytes;
									}
									break;
								}
								// ZFILE frame end
							}
						} else if (buf[idx] == 'C') {
							// BIN32 frame
							_zframe.mode = zmodem_frame_t::ZBIN32;
						} else {
							// unknown frame
__zframe_error:
							// reset states
							_reset();
							return consumed_bytes;
						}
					}

				} else {
					++idx;
					// discard unknown byte
					_zframe.reset();
					consumed_bytes += idx;
					len -= idx;
					buf += idx;
					continue;
				}

__zframe_end:
				consumed_bytes += idx;
				len -= idx;
				buf += idx;
#if DEBUG
				static const char *_ZMODE_NAME[] = {
					"HEX",
					"BIN16",
					"BIN32",
				};
				_log_printf("==>ZFrame:%s(%d) MODE:%s\n", _zframe_name(_zframe.type), _zframe.type, _ZMODE_NAME[_zframe.mode]);
#endif
				switch(_zframe.type) {
				case zmodem::ZRQINIT:
					_write_hex_frame(zmodem::ZRINIT);
					break;
				case zmodem::ZDATA:
					_trans_bytes = 0;
					_receive_data = true;
					_trans_begin = GetTickCount64();
					_trans_begin_bytes = 0;
					break;
				case zmodem::ZEOF:
					if (_fp) {
						file::close(_fp);
						_fp = nullptr;
						if (_file_time) {
							std::filesystem::last_write_time(_u8_to_u16(_current_file_path), from_sys(std::chrono::system_clock::from_time_t(_file_time)));
						}
					}
#if DEBUG
					_log_printf("ZEOF:%u\n", _zframe.flags);
#endif
					_log_printf("Received %s, %llu/%llu bytes\n\x1b[0K", _current_file_path.c_str(), _trans_bytes, _file_bytes);
					_write_hex_frame(zmodem::ZRINIT);
					break;
				case zmodem::ZFIN:
					if (_working_mode == MODE_RZ) {
						_write_hex_frame(zmodem::ZFIN);
						_reset();
						_working_mode = MODE_FIN;
					} else {
						_output_data.append("OO");
						_reset();
					}
					return consumed_bytes;

				case zmodem::ZFILE: {
						_current_file_name = _zframe.ext_data.c_str();
						if (_current_file_name.empty()) {
							// no name, should skip the frame?
							break;
						}
						_current_file_path = _current_file_name;
						char *end_pos = nullptr;
						_file_bytes = strtoull(_zframe.ext_data.c_str() + _current_file_name.size() + 1, &end_pos, 10);
						while(end_pos && *end_pos == ' ') {
							++end_pos;
						}
						if (end_pos) {
							_file_time = strtoull(end_pos, nullptr, 8);
						} else {
							_file_time = 0;
						}
#ifdef _DEBUG
						auto crc = be_helper.read(_zframe.ext_data_crc);
						auto c0 = updcrc(0, updcrc(0, updcrc(&_zframe, 5, 0)));
						auto c1 = updcrc(_zframe.ext_data.data(), _zframe.ext_data.size(), 0);
						c1 = updcrc(zmodem::ZCRCW, c1);
						c1 = updcrc("\0\0", 2, c1);
#endif
						if (_on_file_arrival) {
							if (!_on_file_arrival(_current_file_path)) {
								_write_hex_frame(zmodem::ZSKIP, 0);
								break;
							}
						}
						wchar_t wfile_path[8192] = {};
						cpp::base::str::utf8_to_utf16(_current_file_path.c_str(), _current_file_path.size(), wfile_path, cpp::utils::array_length(wfile_path));
						_fp = file::open(wfile_path, L"w+b");
						if (_fp == nullptr) {
							_log_printf(L"Can not open file: %ls\n", wfile_path);
							_write_hex_frame(zmodem::ZSKIP, 0);
							break;
						}
						_write_hex_frame(zmodem::ZRPOS, 0);
						break;
					}
				case zmodem::ZABORT:
					_output_data.clear();
					_reset();
					return consumed_bytes;

				case zmodem::ZRINIT: {
						if (_working_mode == MODE_SZ_ZFILE) {
							// already sending
							break;
						}
						if (_fp) {
							// already send file
							file::close(_fp);
							_fp = nullptr;
							_log_printf("Sent %s, %llu/%llu bytes\n\x1b[0K", _current_file_path.c_str(), _trans_bytes, _file_bytes);
							if (_sending_files.empty() || !_begin_send_file()) {
								_write_hex_frame(zmodem::ZFIN);
								_working_mode = MODE_SZ;
							}
							break;
						}
						// trigger select file
						if (!_begin_send_file()) {
							_write_hex_frame(zmodem::ZFIN);
							_working_mode = MODE_SZ;
							return consumed_bytes;
						}
						break;
					}
				case zmodem::ZSKIP:
					if (_fp) {
						file::close(_fp);
						_fp = nullptr;
						_log_printf("Skipped %s\n\x1b[0K", _current_file_path.c_str());
						if (!_sending_files.empty() && _begin_send_file()) {
							// send another file
							break;
						}
						// already send file
						_write_hex_frame(zmodem::ZFIN);
						_working_mode = MODE_SZ;
						break;
					}
					break;
				case zmodem::ZRPOS: {
						auto offset = _zframe.flags;
#if DEBUG
						_log_printf("ZRPOS: %u\n", offset);
#endif
						file::seek(_fp, offset, file::seek_set);
						_trans_bytes = 0;
						_trans_begin_bytes = 0;
						_trans_begin = GetTickCount64();

						_write_bin16_frame(zmodem::ZDATA, offset);
						_sending_data = true;
					}
					break;
				case zmodem::ZSINIT:
					break;
				case zmodem::ZACK:
					break;
				case zmodem::ZNAK:
					break;
				case zmodem::ZFERR:
					break;
				case zmodem::ZCRC:
					break;
				case zmodem::ZCHALLENGE:
					break;
				case zmodem::ZCOMPL:
					break;
				case zmodem::ZCAN:
					break;
				case zmodem::ZFREECNT:
					break;
				case zmodem::ZCOMMAND:
					break;
				case zmodem::ZSTDERR:
					break;
				}
			}
		}
		return consumed_bytes;
	}

	std::wstring _u8_to_u16(const std::string &str) {
		std::wstring w;
		w.resize(str.size());
		w.resize(cpp::base::str::utf8_to_utf16(str.data(), str.size(), w.data(), w.size()));
		return w;
	}

	std::function<bool(std::string &)> _on_file_arrival;
	std::function<bool(std::wstring &)> _on_send_file;
	std::function<void(bool, const std::string &, const std::string &, uint64_t, uint64_t, double)> _on_progress;
	char _last_recv_ch{0};
public:
	bool working() const {
		return _working_mode > MODE_NONE;
	}

	bool cancel() {
		if (_working_mode != MODE_NONE) {
			if (_working_mode == MODE_SZ || _working_mode == MODE_SZ_ZFILE) {
				_output_data.resize(0);
			}
			_output_data.append("\x18\x18\x18\x18\x18\x18\x18\x18\x08\x08\x08\x08\x08\x08\x08\x08\0", 17);
			_reset();
			return true;
		}
		return false;
	}

	bool send(const std::function<int32_t(const uint8_t *, size_t)> &write) {
		size_t block_count = 0;
__send_again:
		_do_send_file();
		if (_output_data.empty()) {
			return false;
		}
		size_t write_offset = 0;
		size_t send_count = 0;
		while(write_offset < _output_data.size()) {
			++send_count;
			auto written = write((const uint8_t *)_output_data.data() + write_offset, _output_data.size() - write_offset);
			if (written<0) {
				Sleep(1);
				continue;
			}
			write_offset += written;
		}
		_output_data.resize(0);

		if (send_count <= 1 && _sending_data && block_count < 16) {
			++block_count;
			goto __send_again;
		}
		return send_count <= 1;
	}

	void add_sending_file(std::wstring &&path) {
		_sending_files.emplace_back(path);
	}

	_this_t &on_send_file(std::function<bool(std::wstring &)> &&cb) {
		_on_send_file = cb;
		return *this;
	}

	_this_t &on_file_arrival(std::function<bool(std::string &)> &&cb) {
		_on_file_arrival = cb;
		return *this;
	}

	_this_t &on_progress(std::function<void(bool, const std::string &, const std::string &, uint64_t, uint64_t, double)> &&cb) {
		_on_progress = cb;
		return *this;
	}

	template<typename _TSize>
	bool filter_recv(uint8_t *rbuf,  _TSize &rbuf_len) {
		const uint8_t *rz_waiting_pos = nullptr;
		if (_working_mode == MODE_FIN) {
			_working_mode = MODE_NONE;
			if (rbuf_len >=2 && rbuf[0] == 'O' && rbuf[1] == 'O') {
				_last_recv_ch = rbuf[rbuf_len-1];
				memmove(rbuf, rbuf+2, rbuf_len - 2);
				rbuf_len -= 2;
				return false;
			}
			_last_recv_ch = rbuf[rbuf_len-1];
			return false;
		}
		// auto init_len = rbuf_len;
		size_t reserved_header_size = 0;
		if (_working_mode == MODE_NONE) {
			_TSize idx = 0;
			if (_resume_point > 0) {
				auto resume = _resume_point;
				_resume_point = 0;
				switch(resume) {
				case 1:
					goto __find_zmodem_command_1;
				case 2:
					goto __find_zmodem_command_2;
				}
			}
			for(; idx<rbuf_len;) {
				if (rbuf[idx] == 0x1b) {
					++idx;
					if (rbuf[idx] == ']') {
						++idx;
						while(idx < rbuf_len) {
							if (rbuf[idx] == 0x07) {
								++idx;
								break;
							}
							++idx;
						}
						continue;
					}
					if (rbuf[idx] == '>' || rbuf[idx] == '=') {
						++idx;
						continue;
					}

					if (rbuf[idx] == '[') {
						auto escaped_seq = rbuf + idx;
						++idx;
						if (rbuf[idx] == '?') {
							++idx;
						}
						while(idx < rbuf_len) {
							if (isdigit(rbuf[idx]) || rbuf[idx] == ';' || rbuf[idx] == ' ') {
								++idx;
								continue;
							}
							++idx;
							if (memcmp(escaped_seq, "[?2004h", 7) == 0) {
								_working_mode = MODE_NONE;
								return false;
							}
							break;
						}
						continue;
					}
				}
				if (_last_recv_ch == '\r' || _last_recv_ch == '\n') {
					rz_waiting_pos = (uint8_t*)strstr((const char *)rbuf+idx, "rz waiting to receive.");
					if (rz_waiting_pos && rz_waiting_pos - rbuf + 22 <= rbuf_len) {
						while(idx < rbuf_len && (rbuf[idx] == '\r' || rbuf[idx] == '\n')) {
							++idx;
						}
						reserved_header_size = idx;
						idx = rz_waiting_pos - rbuf + 22;
__find_zmodem_command_1:
						while(idx<rbuf_len && (rbuf[idx] == '\r' || rbuf[idx] == '\n')) {
							++idx;
						}

						if (idx>=rbuf_len) {
							_resume_point = 1;
							return false;
						}
						if (idx + 10 < rbuf_len && memcmp(rbuf+idx, "**\x18" "B0", 5) == 0) {
							memmove(rbuf+reserved_header_size, rbuf + idx, rbuf_len - idx + reserved_header_size);
							rbuf_len -= idx - reserved_header_size;
							_working_mode = MODE_SZ;
							goto __handle_zpacket;
						}
					} else if (idx + 3 <= rbuf_len && memcmp(rbuf + idx, "rz\r", 3) == 0) {
						reserved_header_size = idx;
						idx += 3;
__find_zmodem_command_2:
						if (idx>=rbuf_len) {
							_resume_point = 2;
							return false;
						}
						if (idx + 10 < rbuf_len && memcmp(rbuf+idx, "**\x18" "B0", 5) == 0) {
							memmove(rbuf + reserved_header_size, rbuf + idx, rbuf_len - idx);
							rbuf_len -= idx - reserved_header_size;
							_working_mode = MODE_RZ;
							goto __handle_zpacket;
						}
					}
				}
				_last_recv_ch = rbuf[idx];
				++idx;
			}
			return false;
		}
__handle_zpacket:
		auto consumed_bytes = _handle_packet(rbuf + reserved_header_size, rbuf_len - reserved_header_size);
		if ((_TSize(consumed_bytes)) >= rbuf_len) {
			rbuf_len = 0;
			return true;
		}
		rbuf_len -= consumed_bytes;
		memmove(rbuf + reserved_header_size, rbuf + consumed_bytes, rbuf_len - reserved_header_size);
		return rbuf_len == 0;
	};
};

/*
	try {
		// 过滤下行ZModem数据
		if (szrz.filter_recv((uint8_t *)buf, len)) {
			// data handled by szrz
			szrz.send([=](auto buf, auto len) -> int32_t {
				return (int32_t)SSH_CHK(libssh2_channel_write_ex(channel, 0, (const char*)buf, uint32_t(len)));
			});
			if (len == 0) {
				continue;
			}
		}
	} catch(...) {
		szrz.cancel();
		_log_printf("ZModem fatal error!\n");
	}

*/
